
//**************************************************************************
//**
//** p_user.c : Heretic 2 : Raven Software, Corp.
//**
//** $Revision: 458 $
//** $Date: 2009-05-25 15:35:27 +0300 (Mon, 25 May 2009) $
//**
//**************************************************************************

#include "h2stdinc.h"
#include "h2def.h"
#include "p_local.h"
#include "soundst.h"
#include "v_compat.h"	/* for the V_SetPaletteXXX() macros */

// Macros

#define MAXBOB		0x100000	/* 16 pixels of bob */

// Extern Data

extern int		inv_ptr;
extern int		curpos;

// Private Data

static BOOLEAN		onground;
static int		newtorch;	/* used in the torch flicker effect. */
static int		newtorchdelta;

// Global Data

int PStateNormal[NUMCLASSES] =
{
	S_FPLAY,
	S_CPLAY,
	S_MPLAY,
#ifdef ASSASSIN
	S_APLAY,
#endif
	S_PIGPLAY
};

int PStateRun[NUMCLASSES] =
{
	S_FPLAY_RUN1,
	S_CPLAY_RUN1,
	S_MPLAY_RUN1,
#ifdef ASSASSIN
	S_APLAY_RUN1,
#endif
	S_PIGPLAY_RUN1
};

int PStateAttack[NUMCLASSES] =
{
	S_FPLAY_ATK1,
	S_CPLAY_ATK1,
	S_MPLAY_ATK1,
#ifdef ASSASSIN
	S_APLAY_ATK1,
#endif
	S_PIGPLAY_ATK1
};

int PStateAttackEnd[NUMCLASSES] =
{
	S_FPLAY_ATK2,
	S_CPLAY_ATK3,
	S_MPLAY_ATK2,
#ifdef ASSASSIN
	S_APLAY_ATK3,
#endif
	S_PIGPLAY_ATK1
};

int ArmorMax[NUMCLASSES] =
{
		20,
		18,
		16,
#ifdef ASSASSIN
		17,
#endif
		1
};

// Global Functions

void P_PlayerNextArtifact(player_t *player);


//==========================================================================


/*
==================
=
= P_Thrust
=
= moves the given origin along a given angle
=
==================
*/

void P_Thrust(player_t *player, angle_t angle, fixed_t move)
{
	angle >>= ANGLETOFINESHIFT;
	if (player->powers[pw_flight] && !(player->mo->z <= player->mo->floorz))
	{
		player->mo->momx += FixedMul(move, finecosine[angle]);
		player->mo->momy += FixedMul(move, finesine[angle]);
	}
	else if (P_GetThingFloorType(player->mo) == FLOOR_ICE)	// Friction_Low
	{
		player->mo->momx += FixedMul(move>>1, finecosine[angle]);
		player->mo->momy += FixedMul(move>>1, finesine[angle]);
	}
	else
	{
		player->mo->momx += FixedMul(move, finecosine[angle]);
		player->mo->momy += FixedMul(move, finesine[angle]);
	}
}


/*
==================
=
= P_CalcHeight
=
Calculate the walking / running height adjustment
=
==================
*/

static void P_CalcHeight (player_t *player)
{
	int		angle;
	fixed_t	bob;

//
// regular movement bobbing (needs to be calculated for gun swing even
// if not on ground)
// OPTIMIZE: tablify angle

	player->bob = FixedMul (player->mo->momx, player->mo->momx) +
			FixedMul (player->mo->momy,player->mo->momy);
	player->bob >>= 2;
	if (player->bob > MAXBOB)
		player->bob = MAXBOB;
	if (player->mo->flags2 & MF2_FLY && !onground)
	{
		player->bob = FRACUNIT/2;
	}

	if ((player->cheats & CF_NOMOMENTUM))
	{
		player->viewz = player->mo->z + VIEWHEIGHT;
		if (player->viewz > player->mo->ceilingz - 4*FRACUNIT)
			player->viewz = player->mo->ceilingz - 4*FRACUNIT;
		player->viewz = player->mo->z + player->viewheight;
		return;
	}

	angle = (FINEANGLES/20*leveltime) & FINEMASK;
	bob = FixedMul (player->bob/2, finesine[angle]);

//
// move viewheight
//
	if (player->playerstate == PST_LIVE)
	{
		player->viewheight += player->deltaviewheight;
		if (player->viewheight > VIEWHEIGHT)
		{
			player->viewheight = VIEWHEIGHT;
			player->deltaviewheight = 0;
		}
		if (player->viewheight < VIEWHEIGHT/2)
		{
			player->viewheight = VIEWHEIGHT/2;
			if (player->deltaviewheight <= 0)
				player->deltaviewheight = 1;
		}

		if (player->deltaviewheight)
		{
			player->deltaviewheight += FRACUNIT/4;
			if (!player->deltaviewheight)
				player->deltaviewheight = 1;
		}
	}

	if (player->morphTics)
	{
		player->viewz = player->mo->z + player->viewheight - (20*FRACUNIT);
	}
	else
	{
		player->viewz = player->mo->z + player->viewheight + bob;
	}
	if (player->mo->floorclip && player->playerstate != PST_DEAD
		&& player->mo->z <= player->mo->floorz)
	{
		player->viewz -= player->mo->floorclip;
	}
	if (player->viewz > player->mo->ceilingz - 4*FRACUNIT)
	{
		player->viewz = player->mo->ceilingz - 4*FRACUNIT;
	}
	if (player->viewz < player->mo->floorz + 4*FRACUNIT)
	{
		player->viewz = player->mo->floorz + 4*FRACUNIT;
	}
}

/*
=================
=
= P_MovePlayer
=
=================
*/

static void P_MovePlayer(player_t *player)
{
	int look;
	int fly;
	ticcmd_t *cmd;

	cmd = &player->cmd;
	player->mo->angle += (cmd->angleturn<<16);

	onground = (player->mo->z <= player->mo->floorz
			|| (player->mo->flags2&MF2_ONMOBJ));

	if (cmd->forwardmove)
	{
		if (onground || player->mo->flags2 & MF2_FLY)
		{
			P_Thrust(player, player->mo->angle, cmd->forwardmove*2048);
		}
		else
		{
			P_Thrust(player, player->mo->angle, FRACUNIT>>8);
		}
	}
	if (cmd->sidemove)
	{
		if (onground || player->mo->flags2 & MF2_FLY)
		{
			P_Thrust(player, player->mo->angle - ANG90, cmd->sidemove*2048);
		}
		else
		{
			P_Thrust(player, player->mo->angle, FRACUNIT>>8);
		}
	}
	if (cmd->forwardmove || cmd->sidemove)
	{
		if (player->mo->state == &states[PStateNormal[player->playerclass]])
		{
			P_SetMobjState(player->mo, PStateRun[player->playerclass]);
		}
	}

	look = cmd->lookfly & 15;
	if (look > 7)
	{
		look -= 16;
	}
	if (look)
	{
		if (look == TOCENTER)
		{
			player->centering = true;
		}
		else
		{
			player->lookdir += 5*look;
			if (player->lookdir > 90 || player->lookdir < -110)
			{
				player->lookdir -= 5*look;
			}
		}
	}
	if (player->centering)
	{
		if (player->lookdir > 0)
		{
			player->lookdir -= 8;
		}
		else if (player->lookdir < 0)
		{
			player->lookdir += 8;
		}
		if (abs(player->lookdir) < 8)
		{
			player->lookdir = 0;
			player->centering = false;
		}
	}
	fly = cmd->lookfly>>4;
	if (fly > 7)
	{
		fly -= 16;
	}
	if (fly && player->powers[pw_flight])
	{
		if (fly != TOCENTER)
		{
			player->flyheight = fly*2;
			if (!(player->mo->flags2 & MF2_FLY))
			{
				player->mo->flags2 |= MF2_FLY;
				player->mo->flags |= MF_NOGRAVITY;
				if (player->mo->momz <= -39*FRACUNIT)
				{ // stop falling scream
					S_StopSound(player->mo);
				}
			}
		}
		else
		{
			player->mo->flags2 &= ~MF2_FLY;
			player->mo->flags &= ~MF_NOGRAVITY;
		}
	}
	else if (fly > 0)
	{
		P_PlayerUseArtifact(player, arti_fly);
	}
	if (player->mo->flags2 & MF2_FLY)
	{
		player->mo->momz = player->flyheight*FRACUNIT;
		if (player->flyheight)
		{
			player->flyheight /= 2;
		}
	}
}

//==========================================================================
//
// P_DeathThink
//
//==========================================================================

static void P_DeathThink(player_t *player)
{
	int dir;
	angle_t delta;
	int lookDelta;

	P_MovePsprites(player);

	onground = (player->mo->z <= player->mo->floorz);
	if (player->mo->type == MT_BLOODYSKULL || player->mo->type == MT_ICECHUNK)
	{ // Flying bloody skull or flying ice chunk
		player->viewheight = 6*FRACUNIT;
		player->deltaviewheight = 0;
		//player->damagecount = 20;
		if (onground)
		{
			if (player->lookdir < 60)
			{
				lookDelta = (60 - player->lookdir)/8;
				if (lookDelta < 1 && (leveltime & 1))
				{
					lookDelta = 1;
				}
				else if (lookDelta > 6)
				{
					lookDelta = 6;
				}
				player->lookdir += lookDelta;
			}
		}
	}
	else if (!(player->mo->flags2 & MF2_ICEDAMAGE))
	{ // Fall to ground (if not frozen)
		player->deltaviewheight = 0;
		if (player->viewheight > 6*FRACUNIT)
		{
			player->viewheight -= FRACUNIT;
		}
		if (player->viewheight < 6*FRACUNIT)
		{
			player->viewheight = 6*FRACUNIT;
		}
		if (player->lookdir > 0)
		{
			player->lookdir -= 6;
		}
		else if (player->lookdir < 0)
		{
			player->lookdir += 6;
		}
		if (abs(player->lookdir) < 6)
		{
			player->lookdir = 0;
		}
	}
	P_CalcHeight(player);

	if (player->attacker && player->attacker != player->mo)
	{ // Watch killer
		dir = P_FaceMobj(player->mo, player->attacker, &delta);
		if (delta < ANGLE_1*10)
		{ // Looking at killer, so fade damage and poison counters
			if (player->damagecount)
			{
				player->damagecount--;
			}
			if (player->poisoncount)
			{
				player->poisoncount--;
			}
		}
		delta = delta/8;
		if (delta > ANGLE_1*5)
		{
			delta = ANGLE_1*5;
		}
		if (dir)
		{ // Turn clockwise
			player->mo->angle += delta;
		}
		else
		{ // Turn counter clockwise
			player->mo->angle -= delta;
		}
	}
	else if (player->damagecount || player->poisoncount)
	{
		if (player->damagecount)
		{
			player->damagecount--;
		}
		else
		{
			player->poisoncount--;
		}
	}

	if (player->cmd.buttons & BT_USE)
	{
		if (player == &players[consoleplayer])
		{
			V_SetPaletteBase();
			inv_ptr = 0;
			curpos = 0;
			newtorch = 0;
			newtorchdelta = 0;
		}
		player->playerstate = PST_REBORN;
		player->mo->special1 = player->playerclass;
		if (player->mo->special1 > 2)
		{
		// O.S. --  FIXME: HARDCODED NUMBER: 2 == PCLASS_MAGE
			player->mo->special1 = 0;
		}
		// Let the mobj know the player has entered the reborn state.
		// Some mobjs need to know when it's ok to remove themselves.
		player->mo->special2 = 666;
	}
}

//----------------------------------------------------------------------------
//
// PROC P_MorphPlayerThink
//
//----------------------------------------------------------------------------

static void P_MorphPlayerThink(player_t *player)
{
	mobj_t *pmo;

	if (player->morphTics & 15)
	{
		return;
	}
	pmo = player->mo;
	if (!(pmo->momx + pmo->momy) && P_Random() < 64)
	{ // Snout sniff
		P_SetPspriteNF(player, ps_weapon, S_SNOUTATK2);
		S_StartSound(pmo, SFX_PIG_ACTIVE1); // snort
		return;
	}
	if (P_Random() < 48)
	{
		if (P_Random() < 128)
		{
			S_StartSound(pmo, SFX_PIG_ACTIVE1);
		}
		else
		{
			S_StartSound(pmo, SFX_PIG_ACTIVE2);
		}
	}
}

//----------------------------------------------------------------------------
//
// FUNC P_GetPlayerNum
//
//----------------------------------------------------------------------------

int P_GetPlayerNum(player_t *player)
{
	int i;

	for (i = 0; i < MAXPLAYERS; i++)
	{
		if (player == &players[i])
		{
			return i;
		}
	}
	return 0;
}

//----------------------------------------------------------------------------
//
// FUNC P_UndoPlayerMorph
//
//----------------------------------------------------------------------------

BOOLEAN P_UndoPlayerMorph(player_t *player)
{
	mobj_t *fog;
	mobj_t *mo;
	mobj_t *pmo;
	fixed_t x;
	fixed_t y;
	fixed_t z;
	angle_t angle;
	int playerNum;
	weapontype_t weapon;
	int oldFlags;
	int oldFlags2;
	int oldBeast;

	pmo = player->mo;
	x = pmo->x;
	y = pmo->y;
	z = pmo->z;
	angle = pmo->angle;
	weapon = pmo->special1;
	oldFlags = pmo->flags;
	oldFlags2 = pmo->flags2;
	oldBeast = pmo->type;
	P_SetMobjState(pmo, S_FREETARGMOBJ);
	playerNum = P_GetPlayerNum(player);
	switch (PlayerClasses[playerNum])
	{
	case PCLASS_FIGHTER:
		mo = P_SpawnMobj(x, y, z, MT_PLAYER_FIGHTER);
		break;
	case PCLASS_CLERIC:
		mo = P_SpawnMobj(x, y, z, MT_PLAYER_CLERIC);
		break;
	case PCLASS_MAGE:
		mo = P_SpawnMobj(x, y, z, MT_PLAYER_MAGE);
		break;
#ifdef ASSASSIN
	case PCLASS_ASS:
		mo = P_SpawnMobj(x, y, z, MT_PLAYER_ASS);
		break;
#endif
	default:
		I_Error("P_UndoPlayerMorph:  Unknown player class %d\n", player->playerclass);
		mo = NULL;	/* avoid compiler warning */
		break;
	}
	if (P_TestMobjLocation(mo) == false)
	{ // Didn't fit
		P_RemoveMobj(mo);
		mo = P_SpawnMobj(x, y, z, oldBeast);
		mo->angle = angle;
		mo->health = player->health;
		mo->special1 = weapon;
		mo->player = player;
		mo->flags = oldFlags;
		mo->flags2 = oldFlags2;
		player->mo = mo;
		player->morphTics = 2*35;
		return false;
	}
	if (player->playerclass == PCLASS_FIGHTER)
	{
		// The first type should be blue, and the third should be the
		// Fighter's original gold color
		if (playerNum == 0)
		{
			mo->flags |= 2<<MF_TRANSSHIFT;
		}
		else if (playerNum != 2)
		{
			mo->flags |= playerNum<<MF_TRANSSHIFT;
		}
	}
	else if (playerNum)
	{ // Set color translation bits for player sprites
		mo->flags |= playerNum<<MF_TRANSSHIFT;
	}
	mo->angle = angle;
	mo->player = player;
	mo->reactiontime = 18;
	if (oldFlags2 & MF2_FLY)
	{
		mo->flags2 |= MF2_FLY;
		mo->flags |= MF_NOGRAVITY;
	}
	player->morphTics = 0;
	player->health = mo->health = MAXHEALTH;
	player->mo = mo;
	player->playerclass = PlayerClasses[playerNum];
	angle >>= ANGLETOFINESHIFT;
	fog = P_SpawnMobj(x + 20*finecosine[angle],
			  y + 20*finesine[angle],
			  z + TELEFOGHEIGHT, MT_TFOG);
	S_StartSound(fog, SFX_TELEPORT);
	P_PostMorphWeapon(player, weapon);
	return true;
}


//----------------------------------------------------------------------------
//
// PROC P_PlayerThink
//
//----------------------------------------------------------------------------

void P_PlayerThink(player_t *player)
{
	ticcmd_t *cmd;
	weapontype_t newweapon;
	int floorType;
	mobj_t *pmo;

	// No-clip cheat
	if (player->cheats & CF_NOCLIP)
	{
		player->mo->flags |= MF_NOCLIP;
	}
	else
	{
		player->mo->flags &= ~MF_NOCLIP;
	}
	cmd = &player->cmd;
	if (player->mo->flags & MF_JUSTATTACKED)
	{ // Gauntlets attack auto forward motion
		cmd->angleturn = 0;
		cmd->forwardmove = 0xc800 / 512;
		cmd->sidemove = 0;
		player->mo->flags &= ~MF_JUSTATTACKED;
	}
// messageTics is above the rest of the counters so that messages will 
// 		go away, even in death.
	player->messageTics--; // Can go negative
	if (!player->messageTics || player->messageTics == -1)
	{ // Refresh the screen when a message goes away
		player->ultimateMessage = false; // clear out any chat messages.
		player->yellowMessage = false;
		if (player == &players[consoleplayer])
		{
			BorderTopRefresh = true;
		}
	}
	player->worldTimer++;
	if (player->playerstate == PST_DEAD)
	{
		P_DeathThink(player);
		return;
	}
	if (player->jumpTics)
	{
		player->jumpTics--;
	}
	if (player->morphTics)
	{
		P_MorphPlayerThink(player);
	}
	// Handle movement
	if (player->mo->reactiontime)
	{ // Player is frozen
		player->mo->reactiontime--;
	}
	else
	{
		P_MovePlayer(player);
		pmo = player->mo;
		if (player->powers[pw_speed] && !(leveltime & 1)
			&& P_AproxDistance(pmo->momx, pmo->momy) > 12*FRACUNIT)
		{
			mobj_t *speedMo;
			int playerNum;

			speedMo = P_SpawnMobj(pmo->x, pmo->y, pmo->z, MT_PLAYER_SPEED);
			if (speedMo)
			{
				speedMo->angle = pmo->angle;
				playerNum = P_GetPlayerNum(player);
				if (player->playerclass == PCLASS_FIGHTER)
				{
					// The first type should be blue, and the 
					// third should be the Fighter's original gold color
					if (playerNum == 0)
					{
						speedMo->flags |= 2<<MF_TRANSSHIFT;
					}
					else if (playerNum != 2)
					{
						speedMo->flags |= playerNum<<MF_TRANSSHIFT;
					}
				}
				else if (playerNum)
				{ // Set color translation bits for player sprites
					speedMo->flags |= playerNum<<MF_TRANSSHIFT;
				}
				speedMo->target = pmo;
				speedMo->special1 = player->playerclass;
				if (speedMo->special1 > 2)
				{
				// O.S. --  FIXME: HARDCODED NUMBER: 2 == PCLASS_MAGE
					speedMo->special1 = 0;
				}
				speedMo->sprite = pmo->sprite;
				speedMo->floorclip = pmo->floorclip;
				if (player == &players[consoleplayer])
				{
					speedMo->flags2 |= MF2_DONTDRAW;
				}
			}
		}
	}
	P_CalcHeight(player);
	if (player->mo->subsector->sector->special)
	{
		P_PlayerInSpecialSector(player);
	}
	if ((floorType = P_GetThingFloorType(player->mo)) != FLOOR_SOLID)
	{
		P_PlayerOnSpecialFlat(player, floorType);
	}
	switch (player->playerclass)
	{
	case PCLASS_FIGHTER:
		if (player->mo->momz <= -35*FRACUNIT && player->mo->momz >= -40*FRACUNIT
			&& !player->morphTics
			&& !S_GetSoundPlayingInfo(player->mo, SFX_PLAYER_FIGHTER_FALLING_SCREAM))
		{
			S_StartSound(player->mo, SFX_PLAYER_FIGHTER_FALLING_SCREAM);
		}
		break;
	case PCLASS_CLERIC:
		if (player->mo->momz <= -35*FRACUNIT && player->mo->momz >= -40*FRACUNIT
			&& !player->morphTics
			&& !S_GetSoundPlayingInfo(player->mo, SFX_PLAYER_CLERIC_FALLING_SCREAM))
		{
			S_StartSound(player->mo, SFX_PLAYER_CLERIC_FALLING_SCREAM);
		}
		break;
	case PCLASS_MAGE:
		if (player->mo->momz <= -35*FRACUNIT && player->mo->momz >= -40*FRACUNIT
			&& !player->morphTics
			&& !S_GetSoundPlayingInfo(player->mo, SFX_PLAYER_MAGE_FALLING_SCREAM))
		{
			S_StartSound(player->mo, SFX_PLAYER_MAGE_FALLING_SCREAM);
		}
#ifdef ASSASSIN
	case PCLASS_ASS:
		if (player->mo->momz <= -35*FRACUNIT && player->mo->momz >= -40*FRACUNIT
			&& !player->morphTics
			&& !S_GetSoundPlayingInfo(player->mo, SFX_PLAYER_MAGE_FALLING_SCREAM))
		{
			S_StartSound(player->mo, SFX_PLAYER_MAGE_FALLING_SCREAM);
		}
		break;
#endif
	default:
		break;
	}
	if (cmd->arti)
	{ // Use an artifact
		if ((cmd->arti & AFLAG_JUMP) && onground && !player->jumpTics)
		{
			if (player->morphTics)
			{
				player->mo->momz = 6*FRACUNIT;
			}
			else
			{
				player->mo->momz = 9*FRACUNIT;
			}
			player->mo->flags2 &= ~MF2_ONMOBJ;
			player->jumpTics = 18;
		}
		else if (cmd->arti & AFLAG_SUICIDE)
		{
			P_DamageMobj(player->mo, NULL, NULL, 10000);
		}
		if (cmd->arti == NUMARTIFACTS)
		{ // use one of each artifact (except puzzle artifacts)
			int i;
			for (i = 1; i < arti_firstpuzzitem; i++)
			{
				P_PlayerUseArtifact(player, i);
			}
		}
		else
		{
			P_PlayerUseArtifact(player, cmd->arti & AFLAG_MASK);
		}
	}
	// Check for weapon change
	if (cmd->buttons & BT_SPECIAL)
	{ // A special event has no other buttons
		cmd->buttons = 0;
	}
	if (cmd->buttons & BT_CHANGE && !player->morphTics)
	{
		// The actual changing of the weapon is done when the weapon
		// psprite can do it (A_WeaponReady), so it doesn't happen in
		// the middle of an attack.
		newweapon = (cmd->buttons & BT_WEAPONMASK)>>BT_WEAPONSHIFT;
		if (player->weaponowned[newweapon]
			&& newweapon != player->readyweapon)
		{
			player->pendingweapon = newweapon;
		}
	}
	// Check for use
	if (cmd->buttons & BT_USE)
	{
		if (!player->usedown)
		{
			P_UseLines(player);
			player->usedown = true;
		}
	}
	else
	{
		player->usedown = false;
	}
	// Morph counter
	if (player->morphTics)
	{
		if (!--player->morphTics)
		{ // Attempt to undo the pig
			P_UndoPlayerMorph(player);
		}
	}
	// Cycle psprites
	P_MovePsprites(player);
	// Other Counters
	if (player->powers[pw_invulnerability])
	{
		if (player->playerclass == PCLASS_CLERIC)
		{
			if (!(leveltime & 7) && player->mo->flags & MF_SHADOW
				&& !(player->mo->flags2 & MF2_DONTDRAW))
			{
				player->mo->flags &= ~MF_SHADOW;
				if (!(player->mo->flags & MF_ALTSHADOW))
				{
					player->mo->flags2 |= MF2_DONTDRAW|MF2_NONSHOOTABLE;
				}
			}
			if (!(leveltime & 31))
			{
				if (player->mo->flags2 & MF2_DONTDRAW)
				{
					if (!(player->mo->flags & MF_SHADOW))
					{
						player->mo->flags |= MF_SHADOW|MF_ALTSHADOW;
					}
					else
					{
						player->mo->flags2 &= ~(MF2_DONTDRAW|MF2_NONSHOOTABLE);
					}
				}
				else
				{
					player->mo->flags |= MF_SHADOW;
					player->mo->flags &= ~MF_ALTSHADOW;
				}
			}
		}
		if (!(--player->powers[pw_invulnerability]))
		{
			player->mo->flags2 &= ~(MF2_INVULNERABLE|MF2_REFLECTIVE);
			if (player->playerclass == PCLASS_CLERIC)
			{
				player->mo->flags2 &= ~(MF2_DONTDRAW|MF2_NONSHOOTABLE);
				player->mo->flags &= ~(MF_SHADOW|MF_ALTSHADOW);
			}
		}
	}
	if (player->powers[pw_minotaur])
	{
		player->powers[pw_minotaur]--;
	}
	if (player->powers[pw_infrared])
	{
		player->powers[pw_infrared]--;
	}
	if (player->powers[pw_flight] && netgame)
	{
		if (!--player->powers[pw_flight])
		{
		//	if (player->mo->z != player->mo->floorz) { }
			player->mo->flags2 &= ~MF2_FLY;
			player->mo->flags &= ~MF_NOGRAVITY;
			BorderTopRefresh = true; //make sure the sprite's cleared out
		}
	}
	if (player->powers[pw_speed])
	{
		player->powers[pw_speed]--;
	}
	if (player->damagecount)
	{
		player->damagecount--;
	}
	if (player->bonuscount)
	{
		player->bonuscount--;
	}
	if (player->poisoncount && !(leveltime & 15))
	{
		player->poisoncount -= 5;
		if (player->poisoncount < 0)
		{
			player->poisoncount = 0;
		}
		P_PoisonDamage(player, player->poisoner, 1, true); 
	}
	// Colormaps
//	if (player->powers[pw_invulnerability])
//	{
//		if (player->powers[pw_invulnerability] > BLINKTHRESHOLD
//			|| (player->powers[pw_invulnerability] & 8))
//		{
//			player->fixedcolormap = INVERSECOLORMAP;
//		}
//		else
//		{
//			player->fixedcolormap = 0;
//		}
//	}
//	else 
	if (player->powers[pw_infrared])
	{
		if (player->powers[pw_infrared] <= BLINKTHRESHOLD)
		{
			if (player->powers[pw_infrared] & 8)
			{
				player->fixedcolormap = 0;
			}
			else
			{
				player->fixedcolormap = 1;
			}
		}
		else if (!(leveltime & 16) && player == &players[consoleplayer])
		{
			if (newtorch)
			{
				if (player->fixedcolormap + newtorchdelta > 7
					|| player->fixedcolormap + newtorchdelta < 1
					|| newtorch == player->fixedcolormap)
				{
					newtorch = 0;
				}
				else
				{
					player->fixedcolormap += newtorchdelta;
				}
			}
			else
			{
				newtorch = (M_Random() & 7) + 1;
				newtorchdelta = (newtorch == player->fixedcolormap) ?
						0 : ((newtorch > player->fixedcolormap) ? 1 : -1);
			}
		}
	}
	else
	{
		player->fixedcolormap = 0;
	}
}


//----------------------------------------------------------------------------
//
// PROC P_ArtiTele
//
//----------------------------------------------------------------------------

static void P_ArtiTele(player_t *player)
{
	int i;
	int selections;
	fixed_t destX;
	fixed_t destY;
	angle_t destAngle;

	if (deathmatch)
	{
		selections = deathmatch_p - deathmatchstarts;
		i = P_Random() % selections;
		destX = deathmatchstarts[i].x<<FRACBITS;
		destY = deathmatchstarts[i].y<<FRACBITS;
		destAngle = ANG45*(deathmatchstarts[i].angle/45);
	}
	else
	{
		destX = playerstarts[0][0].x<<FRACBITS;
		destY = playerstarts[0][0].y<<FRACBITS;
		destAngle = ANG45*(playerstarts[0][0].angle/45);
	}
	P_Teleport(player->mo, destX, destY, destAngle, true);
	if (player->morphTics)
	{ // Teleporting away will undo any morph effects (pig)
		P_UndoPlayerMorph(player);
	}
	//S_StartSound(NULL, sfx_wpnup); // Full volume laugh
}

//----------------------------------------------------------------------------
//
// PROC P_ArtiTeleportOther
//
//----------------------------------------------------------------------------

static void P_ArtiTeleportOther(player_t *player)
{
	mobj_t *mo;

	mo = P_SpawnPlayerMissile(player->mo, MT_TELOTHER_FX1);
	if (mo)
	{
		mo->target = player->mo;
	}
}

static void P_TeleportToPlayerStarts(mobj_t *victim)
{
	int i, selections = 0;
	fixed_t destX, destY;
	angle_t destAngle;

	for (i = 0; i < MAXPLAYERS; i++)
	{
		if (!playeringame[i])
			continue;
		selections++;
	}
	i = P_Random() % selections;
	destX = playerstarts[0][i].x<<FRACBITS;
	destY = playerstarts[0][i].y<<FRACBITS;
	destAngle = ANG45*(playerstarts[0][i].angle/45);
	P_Teleport(victim, destX, destY, destAngle, true);
	//S_StartSound(NULL, sfx_wpnup); // Full volume laugh
}

static void P_TeleportToDeathmatchStarts(mobj_t *victim)
{
	int i, selections;
	fixed_t destX, destY;
	angle_t destAngle;

	selections = deathmatch_p-deathmatchstarts;
	if (selections)
	{
		i = P_Random() % selections;
		destX = deathmatchstarts[i].x<<FRACBITS;
		destY = deathmatchstarts[i].y<<FRACBITS;
		destAngle = ANG45*(deathmatchstarts[i].angle/45);
		P_Teleport(victim, destX, destY, destAngle, true);
		//S_StartSound(NULL, sfx_wpnup); // Full volume laugh
	}
	else
	{
		P_TeleportToPlayerStarts(victim);
	}
}

//----------------------------------------------------------------------------
//
// PROC P_TeleportOther
//
//----------------------------------------------------------------------------

void P_TeleportOther(mobj_t *victim)
{
	if (victim->player)
	{
		if (deathmatch)
			P_TeleportToDeathmatchStarts(victim);
		else
			P_TeleportToPlayerStarts(victim);
	}
	else
	{
		// If death action, run it upon teleport
		if (victim->flags & MF_COUNTKILL && victim->special)
		{
			P_RemoveMobjFromTIDList(victim);
			P_ExecuteLineSpecial(victim->special, victim->args,
							NULL, 0, victim);
			victim->special = 0;
		}

		// Send all monsters to deathmatch spots
		P_TeleportToDeathmatchStarts(victim);
	}
}


#define BLAST_RADIUS_DIST	255*FRACUNIT
#define BLAST_SPEED		20*FRACUNIT
#define BLAST_FULLSTRENGTH	255

void ResetBlasted(mobj_t *mo)
{
	mo->flags2 &= ~MF2_BLASTED;
	if (!(mo->flags & MF_ICECORPSE))
	{
		mo->flags2 &= ~MF2_SLIDE;
	}
}

static void P_BlastMobj(mobj_t *source, mobj_t *victim, fixed_t strength)
{
	angle_t angle, ang;
	mobj_t *mo;
	fixed_t x, y, z;

	angle = R_PointToAngle2(source->x, source->y, victim->x, victim->y);
	angle >>= ANGLETOFINESHIFT;
	if (strength < BLAST_FULLSTRENGTH)
	{
		victim->momx = FixedMul(strength, finecosine[angle]);
		victim->momy = FixedMul(strength, finesine[angle]);
		if (victim->player)
		{
			// Players handled automatically
		}
		else
		{
			victim->flags2 |= MF2_SLIDE;
			victim->flags2 |= MF2_BLASTED;
		}
	}
	else		// full strength blast from artifact
	{
		if (victim->flags & MF_MISSILE)
		{
			switch (victim->type)
			{
			case MT_SORCBALL1:	// don't blast sorcerer balls
			case MT_SORCBALL2:
			case MT_SORCBALL3:
				return;
			case MT_MSTAFF_FX2:	// Reflect to originator
				victim->special1 = (intptr_t)victim->target;
				victim->target = source;
				break;
			default:
				break;
			}
		}
		if (victim->type == MT_HOLY_FX)
		{
			if ((mobj_t *)(victim->special1) == source)
			{
				victim->special1 = (intptr_t)victim->target;
				victim->target = source;
			}
		}
		victim->momx = FixedMul(BLAST_SPEED, finecosine[angle]);
		victim->momy = FixedMul(BLAST_SPEED, finesine[angle]);

		// Spawn blast puff
		ang = R_PointToAngle2(victim->x, victim->y, source->x, source->y);
		ang >>= ANGLETOFINESHIFT;
		x = victim->x + FixedMul(victim->radius + FRACUNIT, finecosine[ang]);
		y = victim->y + FixedMul(victim->radius + FRACUNIT, finesine[ang]);
		z = victim->z - victim->floorclip + (victim->height>>1);
		mo = P_SpawnMobj(x, y, z, MT_BLASTEFFECT);
		if (mo)
		{
			mo->momx = victim->momx;
			mo->momy = victim->momy;
		}

		if (victim->flags & MF_MISSILE)
		{
			victim->momz = 8*FRACUNIT;
			mo->momz = victim->momz;
		}
		else
		{
			victim->momz = (1000/victim->info->mass)<<FRACBITS;
		}
		if (victim->player)
		{
			// Players handled automatically
		}
		else
		{
			victim->flags2 |= MF2_SLIDE;
			victim->flags2 |= MF2_BLASTED;
		}
	}
}

// Blast all mobj things away
static void P_BlastRadius(player_t *player)
{
	mobj_t *mo;
	mobj_t *pmo = player->mo;
	thinker_t *think;
	fixed_t dist;

	S_StartSound(pmo, SFX_ARTIFACT_BLAST);
	P_NoiseAlert(player->mo, player->mo);

	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != P_MobjThinker)
		{ // Not a mobj thinker
			continue;
		}
		mo = (mobj_t *)think;
		if ((mo == pmo) || (mo->flags2 & MF2_BOSS))
		{ // Not a valid monster
			continue;
		}
		if ((mo->type == MT_POISONCLOUD) ||	// poison cloud
			(mo->type == MT_HOLY_FX) ||	// holy fx
			(mo->flags&MF_ICECORPSE))	// frozen corpse
		{
			// Let these special cases go
		}
		else if ((mo->flags & MF_COUNTKILL) &&
				(mo->health <= 0))
		{
			continue;
		}
		else if (!(mo->flags & MF_COUNTKILL) &&
				!(mo->player) &&
				!(mo->flags & MF_MISSILE))
		{	// Must be monster, player, or missile
			continue;
		}
		if (mo->flags2 & MF2_DORMANT)
		{
			continue;		// no dormant creatures
		}
		if ((mo->type == MT_WRAITHB) && (mo->flags2 & MF2_DONTDRAW))
		{
			continue;		// no underground wraiths
		}
		if ((mo->type == MT_SPLASHBASE) ||
			(mo->type == MT_SPLASH))
		{
			continue;
		}
		if (mo->type == MT_SERPENT || mo->type == MT_SERPENTLEADER)
		{
			continue;
		}
		dist = P_AproxDistance(pmo->x - mo->x, pmo->y - mo->y);
		if (dist > BLAST_RADIUS_DIST)
		{ // Out of range
			continue;
		}
		P_BlastMobj(pmo, mo, BLAST_FULLSTRENGTH);
	}
}


#define HEAL_RADIUS_DIST	255*FRACUNIT

// Do class specific effect for everyone in radius
static BOOLEAN P_HealRadius(player_t *player)
{
	mobj_t *mo;
	mobj_t *pmo = player->mo;
	thinker_t *think;
	fixed_t dist;
	int effective = false;
	int amount;

	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != P_MobjThinker)
		{ // Not a mobj thinker
			continue;
		}
		mo = (mobj_t *)think;

		if (!mo->player)
			continue;
		if (mo->health <= 0)
			continue;
		dist = P_AproxDistance(pmo->x - mo->x, pmo->y - mo->y);
		if (dist > HEAL_RADIUS_DIST)
		{ // Out of range
			continue;
		}

		switch (player->playerclass)
		{
		case PCLASS_FIGHTER:	// Radius armor boost
			if ((P_GiveArmor(mo->player, ARMOR_ARMOR, 1)) ||
				(P_GiveArmor(mo->player, ARMOR_SHIELD, 1)) ||
				(P_GiveArmor(mo->player, ARMOR_HELMET, 1)) ||
				(P_GiveArmor(mo->player, ARMOR_AMULET, 1)))
			{
				effective = true;
				S_StartSound(mo, SFX_MYSTICINCANT);
			}
			break;
		case PCLASS_CLERIC:	// Radius heal
			amount = 50 + (P_Random() % 50);
			if (P_GiveBody(mo->player, amount))
			{
				effective = true;
				S_StartSound(mo, SFX_MYSTICINCANT);
			}
			break;
		case PCLASS_MAGE:	// Radius mana boost
			amount = 50 + (P_Random() % 50);
			if ((P_GiveMana(mo->player, MANA_1, amount)) ||
				(P_GiveMana(mo->player, MANA_2, amount)))
			{
				effective = true;
				S_StartSound(mo, SFX_MYSTICINCANT);
			}
			break;
#ifdef ASSASSIN
		case PCLASS_ASS:	// Also Radius heal
			amount = 50 + (P_Random() % 50);
			if (P_GiveBody(mo->player, amount))
			{
				effective = true;
				S_StartSound(mo, SFX_MYSTICINCANT);
			}
			break;
#endif
		case PCLASS_PIG:
		default:
			break;
		}
	}
	return effective;
}


//----------------------------------------------------------------------------
//
// PROC P_PlayerNextArtifact
//
//----------------------------------------------------------------------------

void P_PlayerNextArtifact(player_t *player)
{
	if (player == &players[consoleplayer])
	{
		inv_ptr--;
		if (inv_ptr < 6)
		{
			curpos--;
			if (curpos < 0)
			{
				curpos = 0;
			}
		}
		if (inv_ptr < 0)
		{
			inv_ptr = player->inventorySlotNum - 1;
			if (inv_ptr < 6)
			{
				curpos = inv_ptr;
			}
			else
			{
				curpos = 6;
			}
		}
		player->readyArtifact =
			player->inventory[inv_ptr].type;
	}
}

//----------------------------------------------------------------------------
//
// PROC P_PlayerRemoveArtifact
//
//----------------------------------------------------------------------------

void P_PlayerRemoveArtifact(player_t *player, int slot)
{
	int i;

	player->artifactCount--;
	if (!(--player->inventory[slot].count))
	{ // Used last of a type - compact the artifact list
		player->readyArtifact = arti_none;
		player->inventory[slot].type = arti_none;
		for (i = slot + 1; i < player->inventorySlotNum; i++)
		{
			player->inventory[i-1] = player->inventory[i];
		}
		player->inventorySlotNum--;
		if (player == &players[consoleplayer])
		{ // Set position markers and get next readyArtifact
			inv_ptr--;
			if (inv_ptr < 6)
			{
				curpos--;
				if (curpos < 0)
				{
					curpos = 0;
				}
			}
			if (inv_ptr >= player->inventorySlotNum)
			{
				inv_ptr = player->inventorySlotNum - 1;
			}
			if (inv_ptr < 0)
			{
				inv_ptr = 0;
			}
			player->readyArtifact =
				player->inventory[inv_ptr].type;
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC P_PlayerUseArtifact
//
//----------------------------------------------------------------------------

void P_PlayerUseArtifact(player_t *player, artitype_t arti)
{
	int i;

	for (i = 0; i < player->inventorySlotNum; i++)
	{
		if (player->inventory[i].type == arti)
		{ // Found match - try to use
			if (P_UseArtifact(player, arti))
			{ // Artifact was used - remove it from inventory
				P_PlayerRemoveArtifact(player, i);
				if (player == &players[consoleplayer])
				{
					if (arti < arti_firstpuzzitem)
					{
						S_StartSound(NULL, SFX_ARTIFACT_USE);
					}
					else
					{
						S_StartSound(NULL, SFX_PUZZLE_SUCCESS);
					}
					ArtifactFlash = 4;
				}
			}
			else if (arti < arti_firstpuzzitem)
			{ // Unable to use artifact, advance pointer
				P_PlayerNextArtifact(player);
			}
			break;
		}
	}
}

//==========================================================================
//
// P_UseArtifact
//
// Returns true if the artifact was used.
//
//==========================================================================

BOOLEAN P_UseArtifact(player_t *player, artitype_t arti)
{
	mobj_t *mo;
	angle_t angle;
	int i;
	int count;

	switch (arti)
	{
	case arti_invulnerability:
		if (!P_GivePower(player, pw_invulnerability))
		{
			return false;
		}
		break;
	case arti_health:
		if (!P_GiveBody(player, 25))
		{
			return false;
		}
		break;
	case arti_superhealth:
		if (!P_GiveBody(player, 100))
		{
			return false;
		}
		break;
	case arti_healingradius:
		if (!P_HealRadius(player))
		{
			return false;
		}
		break;
	case arti_torch:
		if (!P_GivePower(player, pw_infrared))
		{
			return false;
		}
		break;
	case arti_egg:
		mo = player->mo;
		P_SpawnPlayerMissile(mo, MT_EGGFX);
		P_SPMAngle(mo, MT_EGGFX, mo->angle - (ANG45/6));
		P_SPMAngle(mo, MT_EGGFX, mo->angle + (ANG45/6));
		P_SPMAngle(mo, MT_EGGFX, mo->angle - (ANG45/3));
		P_SPMAngle(mo, MT_EGGFX, mo->angle + (ANG45/3));
		break;
	case arti_fly:
		if (!P_GivePower(player, pw_flight))
		{
			return false;
		}
		if (player->mo->momz <= -35*FRACUNIT)
		{ // stop falling scream
			S_StopSound(player->mo);
		}
		break;
	case arti_summon:
		mo = P_SpawnPlayerMissile(player->mo, MT_SUMMON_FX);
		if (mo)
		{
			mo->target = player->mo;
			mo->special1 = (intptr_t)(player->mo);
			mo->momz = 5*FRACUNIT;
		}
		break;
	case arti_teleport:
		P_ArtiTele(player);
		break;
	case arti_teleportother:
		P_ArtiTeleportOther(player);
		break;
	case arti_poisonbag:
		angle = player->mo->angle>>ANGLETOFINESHIFT;
		if (player->playerclass == PCLASS_CLERIC)
		{
			mo = P_SpawnMobj(player->mo->x + 16*finecosine[angle],
					 player->mo->y + 24*finesine[angle],
					 player->mo->z - player->mo->floorclip + 8*FRACUNIT,
					 MT_POISONBAG);
			if (mo)
			{
				mo->target = player->mo;
			}
		}
		else if (player->playerclass == PCLASS_MAGE)
		{
			mo = P_SpawnMobj(player->mo->x + 16*finecosine[angle],
					 player->mo->y + 24*finesine[angle],
					 player->mo->z - player->mo->floorclip + 8*FRACUNIT,
					 MT_FIREBOMB);
			if (mo)
			{
				mo->target = player->mo;
			}
		}
		else // PCLASS_FIGHTER, obviously (also pig, not so obviously)
		{
			mo = P_SpawnMobj(player->mo->x, player->mo->y,
					 player->mo->z - player->mo->floorclip + 35*FRACUNIT,
					 MT_THROWINGBOMB);
			if (mo)
			{
				mo->angle = player->mo->angle + (((P_Random() & 7) - 4) << 24);
				mo->momz = 4*FRACUNIT + ((player->lookdir) << (FRACBITS - 4));
				mo->z += player->lookdir << (FRACBITS - 4);
				P_ThrustMobj(mo, mo->angle, mo->info->speed);
				mo->momx += player->mo->momx>>1;
				mo->momy += player->mo->momy>>1;
				mo->target = player->mo;
				mo->tics -= P_Random() & 3;
				P_CheckMissileSpawn(mo);
			}
		}
		break;
	case arti_speed:
		if (!P_GivePower(player, pw_speed))
		{
			return false;
		}
		break;
	case arti_boostmana:
		if (!P_GiveMana(player, MANA_1, MAX_MANA))
		{
			if (!P_GiveMana(player, MANA_2, MAX_MANA))
			{
				return false;
			}
		}
		else
		{
			P_GiveMana(player, MANA_2, MAX_MANA);
		}
		break;
	case arti_boostarmor:
		count = 0;
		for (i = 0; i < NUMARMOR; i++)
		{
			count += P_GiveArmor(player, i, 1); // 1 point per armor type
		}
		if (!count)
		{
			return false;
		}
		break;
	case arti_blastradius:
		P_BlastRadius(player);
		break;

	case arti_puzzskull:
	case arti_puzzgembig:
	case arti_puzzgemred:
	case arti_puzzgemgreen1:
	case arti_puzzgemgreen2:
	case arti_puzzgemblue1:
	case arti_puzzgemblue2:
	case arti_puzzbook1:
	case arti_puzzbook2:
	case arti_puzzskull2:
	case arti_puzzfweapon:
	case arti_puzzcweapon:
	case arti_puzzmweapon:
	case arti_puzzgear1:
	case arti_puzzgear2:
	case arti_puzzgear3:
	case arti_puzzgear4:
		if (P_UsePuzzleItem(player, arti - arti_firstpuzzitem))
		{
			return true;
		}
		else
		{
			P_SetYellowMessage(player, TXT_USEPUZZLEFAILED, false);
			return false;
		}
		break;
	default:
		return false;
	}
	return true;
}

//============================================================================
//
// A_SpeedFade
//
//============================================================================

void A_SpeedFade(mobj_t *actor)
{
	actor->flags |= MF_SHADOW;
	actor->flags &= ~MF_ALTSHADOW;
	actor->sprite = actor->target->sprite;
}

